#include "ClassMeta.h"

#include "BasicTypeOperator.h"
#include "Field.h"
#include "Hash.h"
#include "Method.h"

namespace MOE
{
    namespace Core
    {

		const ClassMeta ClassMeta::kNil = ClassMeta();

		// Default constructor.
		// This constructs a ClassMeta for Nil type.
		ClassMeta::ClassMeta()
			: mClassName(kBasicTypeNames[BasicType::kNil])
			, mIsBasicType(true)
			, mBasicType(BasicType::kNil)
			, mCopyElementHandler(BasicTypeOperator::NilOperator::CopyData)
			, mCopyArrayHandler(BasicTypeOperator::NilOperator::CopyArray)
			, mParentClass(kNil)
			, mClassSize(0)
		{
		}

		// Constructor for basic types
		ClassMeta::ClassMeta(const char8* className, BasicType basicTypeEnum, tSize elementSize, CopyDataFunction elementHandler, CopyArrayFunction arrayHandler)
			: mClassName(className)
			, mIsBasicType(true)
			, mBasicType(basicTypeEnum)
			, mCopyElementHandler(elementHandler)
			, mCopyArrayHandler(arrayHandler)
			, mParentClass(kNil)
			, mClassSize(elementSize)
		{
		}

		// Constructor for composite types
		ClassMeta::ClassMeta(const char8* className, tSize elementSize)
			: mClassName(className)
			, mIsBasicType(false)
			, mBasicType(BasicType::kNil)
			, mCopyElementHandler(nullptr)
			, mCopyArrayHandler(nullptr)
			, mParentClass(kNil)
			, mClassSize(elementSize)
		{
		}

		// Constructor for composite types that inherits from a parent class
		ClassMeta::ClassMeta(const ClassMeta& parentClass, const char8* className, tSize elementSize)
			: mClassName(className)
			, mIsBasicType(false)
			, mBasicType(BasicType::kNil)
			, mCopyElementHandler(nullptr)
			, mCopyArrayHandler(nullptr)
			, mParentClass(parentClass)
			, mClassSize(elementSize)
		{
		}

		// Destructor cleans up all Fields and Methods.
		ClassMeta::~ClassMeta()
		{
			for (tFieldMap::value_type& fieldPair : mFields)
			{
				delete fieldPair.second;
			}
			mFields.clear();

			for (tMethodMap::value_type& methodPair : mMethods)
			{
				delete methodPair.second;
			}
			mMethods.clear();
		}

		// Helper function to check if this class is a subclass of another class.
		bool ClassMeta::IsSubclassOf(const ClassMeta& otherMeta) const
		{
			const ClassMeta* currentParent = &GetParentClass();
			while (!currentParent->IsNil())
			{
				if (currentParent == &otherMeta)
				{
					return true;
				}
				currentParent = &currentParent->GetParentClass();
			}
			return false;
		}

        // Adds a field to the class meta.
        void ClassMeta::AddField(Field* field)
		{
			// Cannot add a field for basic type.
			if (mIsBasicType)
			{
				return;
			}

			if (!field)
			{
				return;
			}

			tHashValue fieldNameHash = StringToHash(field->GetName());
			if (mFields.find(fieldNameHash) != mFields.end())
			{
				return;
			}
			mFields.insert(std::make_pair(fieldNameHash, field));
		}

        // Retrieves a field with specified name.
        const Field& ClassMeta::GetField(const char8* fieldName) const
		{
			tHashValue fieldNameHash = StringToHash(fieldName);
			tFieldMap::const_iterator iter = mFields.find(fieldNameHash);
			if (iter != mFields.end())
			{
				return *iter->second;
			}
			return Field::kInvalid;
		}

        // Adds a method to the class meta.
        void ClassMeta::AddMethod(Method* method)
		{
			if (!method)
			{
				return;
			}

			// Unlike field, you can add a method for basic types (so you can do things like ToString() on numerical types).
			tHashValue methodNameHash = StringToHash(method->GetName());
			if (mMethods.find(methodNameHash) != mMethods.end())
			{
				return;
			}
			mMethods.insert(std::make_pair(methodNameHash, method));
		}

        // Retrieves a method with specified name.
        const Method& ClassMeta::GetMethod(const char8* methodName) const
		{
			tHashValue methodNameHash = StringToHash(methodName);
			tMethodMap::const_iterator iter = mMethods.find(methodNameHash);
			if (iter != mMethods.end())
			{
				return *iter->second;
			}
			return Method::kInvalid;
		}

		// Gets the reference of Nth element from a value array.
		tUntypedConstRef ClassMeta::GetElement(tUntypedConstRef arrayRef, tSize n) const
		{
			return (static_cast<const int8*>(arrayRef) + n * mClassSize);
		}

		tUntypedRef ClassMeta::GetElement(tUntypedRef arrayRef, tSize n) const
		{
			return (static_cast<int8*>(arrayRef) + n * mClassSize);
		}

		// Copies the value of source and save it to destination.
		// Both source and destination must be able to hold the object defined by this ClassMeta.
		void ClassMeta::CopyValue(tUntypedConstRef source, tUntypedRef destination) const
		{
			if (mIsBasicType)
			{
				// For basic type, do value copying.
				mCopyElementHandler(source, destination);
			}
			else
			{
				// Iterate over each field and copy them.
				for (const tFieldMap::value_type& fieldPair : mFields)
				{
					tUntypedRef destinationField = fieldPair.second->GetFieldRef(destination);
					fieldPair.second->ReadValue(source, destinationField);
				}
			}
		}

		// Copies an array of N elements from source to destination.
		void ClassMeta::CopyArray(tUntypedConstRef source, tUntypedRef destination, tSize numElements) const
		{
			if (mIsBasicType)
			{
				// For basic type, do array copying.
				mCopyArrayHandler(source, destination, numElements);
			}
			else
			{
				// Iterate over each element and copy them.
				for (tSize i = 0; i < numElements; ++i)
				{
					tUntypedConstRef sourceElement = GetElement(source, i);
					tUntypedRef destinationElement = GetElement(destination, i);
					CopyValue(sourceElement, destinationElement);
				}
			}
		}

        // Iterate over all fields.
		void ClassMeta::ForEachField(std::function<void(const Field&)>& func) const
		{
			for (const tFieldMap::value_type& fieldPair : mFields)
			{
				func(*fieldPair.second);
			}
		}

        // Iterate over all methods.
        void ClassMeta::ForEachMethod(std::function<void(const Method&)>& func) const
		{
			for (const tMethodMap::value_type& methodPair : mMethods)
			{
				func(*methodPair.second);
			}
		}

	}
}
